using System;
using System.Reflection;
using System.Collections.Generic;

#if SILVERLIGHT
namespace SBPweb.RIA.Client.Common.Factories
#else
namespace SBPweb.RIA.Server.Common.Factories
#endif
{
	/// <summary>
	/// Class of ItemFactory
	/// </summary>
    public class ItemFactory
    {

        private static Dictionary<Type, ConstructorInfo> typeCache = new Dictionary<Type, ConstructorInfo>();

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="interfaceTypeName">interface type</param>
		/// <returns>item</returns>
        public static T CreateInstance<T>(string interfaceTypeName)
        {
            return CreateInstance<T>(Assembly.GetCallingAssembly(), interfaceTypeName);
        }

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="interfaceType">interface type</param>
		/// <returns>item</returns>
        public static T CreateInstance<T>(Type interfaceType)
        {
            return CreateInstance<T>(Assembly.GetCallingAssembly(), interfaceType);
        }

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="interfaceTypeName">interface type</param>
		/// <param name="arguments">arguments</param>
		/// <returns>item</returns>
        public static T CreateInstance<T>(string interfaceTypeName, object[] arguments)
        {
            return CreateInstance<T>(Assembly.GetCallingAssembly(), interfaceTypeName, arguments);
        }

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="asm">assembly</param>
		/// <param name="interfaceTypeName">interface type</param>
		/// <returns>item</returns>
		public static T CreateInstance<T>(Assembly asm, string interfaceTypeName)
        {
            return CreateInstance<T>(asm, interfaceTypeName, new Type[] { });
        }

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="asm">assembly</param>
		/// <param name="interfaceType">interface type</param>
		/// <returns>item</returns>
		public static T CreateInstance<T>(Assembly asm, Type interfaceType)
        {
            return CreateInstance<T>(asm, interfaceType, new Type[] { });
        }

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="asm">assembly</param>
		/// <param name="interfaceTypeName">interface type</param>
		/// <param name="arguments">arguments</param>
		/// <returns>item</returns>
		public static T CreateInstance<T>(Assembly asm, string interfaceTypeName, object[] arguments)
        {
            Type type = FindFirstTypeByInterfaceName(asm, interfaceTypeName);
            return GetInstance<T>(asm, type, arguments);
        }

		/// <summary>
		/// Creates new instance
		/// </summary>
		/// <typeparam name="T">type</typeparam>
		/// <param name="asm">assembly</param>
		/// <param name="interfaceType">interface type</param>
		/// <param name="arguments">arguments</param>
		/// <returns>item</returns>
        public static T CreateInstance<T>(Assembly asm, Type interfaceType, object[] arguments)
        {
            Type type = FindFirstTypeByInterface(asm, interfaceType);
            return GetInstance<T>(asm, type, arguments);
        }

        #region Private helper functions

        private static T GetInstance<T>(Assembly asm, Type type, object[] arguments)
        {
            lock (typeCache)
            {
                if (type == null)
                {
                    throw new Exception("There is no type implementing the interface!");
                }

                ConstructorInfo cI = null;
                if (typeCache.ContainsKey(type))
                {
                    cI = typeCache[type];
                }
                else
                {
                    Type[] types = (Type[])Array.CreateInstance(typeof(Type), arguments.Length);
                    int i = 0;
                    foreach (object obj in arguments)
                    {
                        types[i++] = obj.GetType();
                    }

                    cI = type.GetConstructor(types);
                    if (cI == null)
                    {
                        throw new ArithmeticException(string.Format("The type {0} does not have a constructor with the given arguments!", type));
                    }
                    typeCache.Add(type, cI);
                }

                return (T)cI.Invoke(arguments);
            }
        }

        private static Type FindFirstTypeByInterface(Assembly asm, Type interfaceType)
        {
            foreach (Type type in asm.GetTypes())
            {
                if (interfaceType.IsAssignableFrom(type))
                {
                    return type;
                }
            }

            return null;
        }

        private static Type FindFirstTypeByInterfaceName(Assembly asm, string interfaceTypeName)
        {
            foreach (Type type in asm.GetTypes())
            {
                Type interfaceType = type.GetInterface(interfaceTypeName, false);
                if (interfaceType != null)
                {
                    return type;
                }
            }

            return null;
        }

        #endregion

    }

}